package com.suray.rgv60.generate;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.suray.basic.wcs.path.core.Node;
import com.suray.basic.wcs.path.enums.NodeType;
import com.suray.basic.wcs.utils.LoggerType;
import com.suray.rgv.util.RgvLogger;
import com.suray.type.RgvMapTagDirection;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * 地图生成器
 */
@Data
@NoArgsConstructor
public class Rgv60MapStruct {

    @JsonProperty("header")
    private HeaderDTO header;

    //地图作用区域
    @JsonProperty("advancedAreaList")
    private List<Scope> advancedAreaList;

    @JsonProperty("normalPosList")
    private List<NormalPosListDTO> normalPosList;
    //节点
    @JsonProperty("advancedPointList")
    private List<AdvancedPointListDTO> advancedPointList;
    //路线
    @JsonProperty("advancedCurveList")
    private List<AdvancedCurveListDTO> advancedCurveList;
    //码点
    @JsonProperty("tagPosList")
    private List<TagPosListDTO> tagPosList;

    @NoArgsConstructor
    @Data
    public static class HeaderDTO {
        @JsonProperty("mapType")
        private String mapType;
        @JsonProperty("mapName")
        private String mapName;
        @JsonProperty("minPos")
        private MinPosDTO minPos;
        @JsonProperty("maxPos")
        private MaxPosDTO maxPos;
        @JsonProperty("resolution")
        private Double resolution;
        @JsonProperty("version")
        private String version;

        @NoArgsConstructor
        @Data
        public static class MinPosDTO {
            @JsonProperty("x")
            private Double x;
            @JsonProperty("y")
            private Double y;
        }

        @NoArgsConstructor
        @Data
        public static class MaxPosDTO {
            @JsonProperty("x")
            private Double x;
            @JsonProperty("y")
            private Double y;
        }
    }

    @NoArgsConstructor
    @Data
    public static class NormalPosListDTO {
        @JsonProperty("x")
        private Double x;
        @JsonProperty("y")
        private Double y;
    }

    @NoArgsConstructor
    @Data
    public static class AdvancedPointListDTO {
        @JsonProperty("className")
        private String className;
        @JsonProperty("instanceName")
        private String instanceName;
        @JsonProperty("pos")
        private PosDTO pos;
        @JsonProperty("property")
        private List<PropertyDTO> property;
        @JsonProperty("ignoreDir")
        private Boolean ignoreDir;

        @Data
        @NoArgsConstructor
        public static class PosDTO {
            @JsonProperty("x")
            private Double x;
            @JsonProperty("y")
            private Double y;

            PosDTO(Double x, Double y) {
                this.x = x;
                this.y = y;
            }
        }

        @NoArgsConstructor
        @Data
        public static class PropertyDTO {
            @JsonProperty("key")
            private String key;
            @JsonProperty("type")
            private String type;
            @JsonProperty("value")
            private String value;
            @JsonProperty("boolValue")
            private Boolean boolValue;
        }
    }

    @NoArgsConstructor
    @Data
    public static class AdvancedCurveListDTO {
        @JsonProperty("className")
        private String className;
        @JsonProperty("instanceName")
        private String instanceName;
        @JsonProperty("startPos")
        private StartPosDTO startPos;
        @JsonProperty("endPos")
        private EndPosDTO endPos;
        @JsonProperty("controlPos1")
        private ControlPos1DTO controlPos1;
        @JsonProperty("controlPos2")
        private ControlPos2DTO controlPos2;
        @JsonProperty("property")
        private List<PropertyDTO> property;

        @NoArgsConstructor
        @Data
        public static class StartPosDTO {
            @JsonProperty("instanceName")
            private String instanceName;
            @JsonProperty("pos")
            private PosDTO pos;

            @NoArgsConstructor
            @Data
            public static class PosDTO {
                @JsonProperty("x")
                private Double x;
                @JsonProperty("y")
                private Double y;

                PosDTO(Double x, Double y) {
                    this.x = x;
                    this.y = y;
                }
            }
        }

        @NoArgsConstructor
        @Data
        public static class EndPosDTO {
            @JsonProperty("instanceName")
            private String instanceName;
            @JsonProperty("pos")
            private PosDTO pos;

            @NoArgsConstructor
            @Data
            public static class PosDTO {
                @JsonProperty("x")
                private Double x;
                @JsonProperty("y")
                private Double y;

                PosDTO(Double x, Double y) {
                    this.x = x;
                    this.y = y;
                }
            }
        }

        @NoArgsConstructor
        @Data
        public static class ControlPos1DTO {
            @JsonProperty("x")
            private Double x;
            @JsonProperty("y")
            private Double y;
        }

        @NoArgsConstructor
        @Data
        public static class ControlPos2DTO {
            @JsonProperty("x")
            private Double x;
            @JsonProperty("y")
            private Double y;
        }

        @NoArgsConstructor
        @Data
        public static class PropertyDTO {
            @JsonProperty("key")
            private String key;
            @JsonProperty("type")
            private String type;
            @JsonProperty("value")
            private String value;
            @JsonProperty("int32Value")
            private Integer int32Value;
        }
    }

    @NoArgsConstructor
    @Data
    public static class TagPosListDTO {
        @JsonProperty("tagValue")
        private Integer tagValue;
        @JsonProperty("x")
        private Double x;
        @JsonProperty("y")
        private Double y;
        @JsonProperty("angle")
        private Double angle;
        @JsonProperty("isDMTDetected")
        private Boolean isDMTDetected;
    }

    @NoArgsConstructor
    @Data
    public static class Scope {
        @JsonProperty("className")
        private String className;
        @JsonProperty("instanceName")
        private String instanceName;
        @JsonProperty("posGroup")
        private List<PosGroup> posGroup;
        @JsonProperty("dir")
        private float dir;
        @JsonProperty("attribute")
        private Attribute attribute;

        @NoArgsConstructor
        @Data
        public static class PosGroup {
            @JsonProperty("x")
            private Double x;
            @JsonProperty("y")
            private Double y;
        }

        @NoArgsConstructor
        @Data
        public static class Attribute {
            @JsonProperty("colorPen")
            private Long colorPen;
            @JsonProperty("colorBrush")
            private Long colorBrush;
        }
    }

    public Rgv60MapStruct(String name, List<Node> nodes, RgvMapTagDirection direction, List<Scope> advancedAreaList) {
        header = generalHead(name);
        normalPosList = generalNormalPosList();
        this.advancedAreaList = advancedAreaList;
        analyzeNodes(nodes, direction);
    }

    private HeaderDTO generalHead(String name) {
        HeaderDTO head = new HeaderDTO();
        head.mapType = "2D-MAP";
        head.mapName = name;
        HeaderDTO.MaxPosDTO maxPosDTO = new HeaderDTO.MaxPosDTO();
        maxPosDTO.x = 16.6;
        maxPosDTO.y = 5.3;
        head.setMaxPos(maxPosDTO);
        HeaderDTO.MinPosDTO minPosDTO = new HeaderDTO.MinPosDTO();
        minPosDTO.x = 13.64;
        minPosDTO.y = -6.84;
        head.setMinPos(minPosDTO);
        head.resolution = 0.02;
        head.version = "1.0.6";
        return head;
    }

    private List<NormalPosListDTO> generalNormalPosList() {
        List<NormalPosListDTO> normalPosListDTOS = new ArrayList<>();
        NormalPosListDTO normalPosListDTO = new NormalPosListDTO();
        normalPosListDTO.x = 13.64;
        normalPosListDTO.y = 5.3;
        normalPosListDTOS.add(normalPosListDTO);
        normalPosListDTO = new NormalPosListDTO();
        normalPosListDTO.x = 16.6;
        normalPosListDTO.y = -6.78;
        normalPosListDTOS.add(normalPosListDTO);
        normalPosListDTO = new NormalPosListDTO();
        normalPosListDTO.x = 16.6;
        normalPosListDTO.y = -6.84;
        normalPosListDTOS.add(normalPosListDTO);
        return normalPosListDTOS;
    }

    private void analyzeNodes(List<Node> nodes, RgvMapTagDirection direction) {
        advancedPointList = new ArrayList<>();
        advancedCurveList = new ArrayList<>();
        tagPosList = new ArrayList<>();

        Map<Node, NodeInformation> nodeDistanceMap = new HashMap<>();

        Collections.sort(nodes, (node1, node2) -> {
            if (node1.getZ() > node2.getZ()) {
                return 1;
            } else if (node1.getZ() < node2.getZ()) {
                return -1;
            } else {
                if (node1.getX() > node2.getX()) {
                    return 1;
                } else if (node1.getX() < node2.getX()) {
                    return -1;
                } else {
                    if (node1.getY() > node2.getY()) {
                        return 1;
                    } else if (node1.getY() < node2.getY()) {
                        return -1;
                    } else {
                        return 0;
                    }
                }
            }
        });

        nodes.forEach(node -> {
            try {
                nodeDistanceMap.put(node, new NodeInformation(node, nodeDistanceMap));
            } catch (Exception e) {
                RgvLogger.exceptionLog(LoggerType.ERROR, LoggerType.MAP.getValue(), node.toString(), e, this.getClass());
            }
        });

        nodes.forEach(node -> {
            try {
                if (!node.getNodeType().equals(NodeType.B)) {
                    advancedPointList.add(generalPoint(node, nodeDistanceMap));
                    advancedCurveList.addAll(generalCurve(node, nodes, nodeDistanceMap));
                    tagPosList.add(generalTag(node, nodeDistanceMap, direction));
                }
            } catch (Exception e) {
                RgvLogger.exceptionLog(LoggerType.ERROR, LoggerType.MAP.getValue(), node.toString(), e, this.getClass());
            }
        });
    }

    /**
     * 节点
     * 坡道:LocationMark
     * 货位:ActionPoint
     * @return
     * @param node
     * @param nodeDistanceMap
     */
    private AdvancedPointListDTO generalPoint(Node node, Map<Node, NodeInformation> nodeDistanceMap) {
        AdvancedPointListDTO advancedPointListDTO = new AdvancedPointListDTO();
        NodeInformation nodeDistance = nodeDistanceMap.get(node);
        if (nodeDistance == null) {
            throw new RuntimeException("找不到对应的节点信息：" + node);
        }
        advancedPointListDTO.className = nodeDistance.className;
        advancedPointListDTO.instanceName = nodeDistance.nodeName;
        advancedPointListDTO.pos = new AdvancedPointListDTO.PosDTO(nodeDistance.distanceX, nodeDistance.distanceY);
        advancedPointListDTO.ignoreDir = true;

        //"property":[{"key":"spin","type":"bool","value":"ZmFsc2U=","boolValue":false}]
        List<AdvancedPointListDTO.PropertyDTO> propertyDTOS = new ArrayList<>();
        AdvancedPointListDTO.PropertyDTO propertyDTO = new AdvancedPointListDTO.PropertyDTO();
        propertyDTO.key = "spin";
        propertyDTO.type = "bool";
        propertyDTO.value = "ZmFsc2U=";
        propertyDTO.boolValue = false;
        propertyDTOS.add(propertyDTO);
        advancedPointListDTO.property = propertyDTOS;
        return advancedPointListDTO;
    }

    /**
     * 贝塞尔曲线，路线
     * @param node
     * @param nodes
     * @param nodeDistanceMap
     * @return
     */
    private List<AdvancedCurveListDTO> generalCurve(Node node, List<Node> nodes, Map<Node, NodeInformation> nodeDistanceMap) {
        List<AdvancedCurveListDTO> advancedCurveListDTOS = new ArrayList<>();

        if (node.getUp()) {
            Node upNode = findNode(node.getX() - 1, node.getY(), node.getZ(), nodes);
            generalCurve(node, upNode, nodeDistanceMap, false, advancedCurveListDTOS);
        }

        if (node.getDown()) {
            Node downNode = findNode(node.getX() + 1, node.getY(), node.getZ(), nodes);
            generalCurve(node, downNode, nodeDistanceMap, true, advancedCurveListDTOS);
        }

        if (node.getLeft()) {
            Node leftNode = findNode(node.getX(), node.getY() - 1, node.getZ(), nodes);
            generalCurve(node, leftNode, nodeDistanceMap, false, advancedCurveListDTOS);
        }

        if (node.getRight()) {
            Node rightNode = findNode(node.getX(), node.getY() + 1, node.getZ(), nodes);
            generalCurve(node, rightNode, nodeDistanceMap, true, advancedCurveListDTOS);
        }

        return advancedCurveListDTOS;
    }

    /**
     * 贝塞尔曲线，路线
     * @param start
     * @param end
     * @param nodeDistanceMap
     * @param advancedCurveListDTOS
     * @return
     */
    private AdvancedCurveListDTO generalCurve(Node start, Node end, Map<Node, NodeInformation> nodeDistanceMap,
                                              boolean backMove, List<AdvancedCurveListDTO> advancedCurveListDTOS) {
        if (start == null || end == null) {
            return null;
        }

        if (start.getNodeType() == NodeType.B || end.getNodeType() == NodeType.B) {
            return null;
        }

        AdvancedCurveListDTO advancedCurveListDTO = new AdvancedCurveListDTO();
        advancedCurveListDTO.setClassName("BezierPath");
        NodeInformation startDistance = nodeDistanceMap.get(start);
        NodeInformation endDistance = nodeDistanceMap.get(end);
        advancedCurveListDTO.instanceName = startDistance.nodeName + "-" + endDistance.nodeName;

        AdvancedCurveListDTO.StartPosDTO startPosDTO = new AdvancedCurveListDTO.StartPosDTO();
        AdvancedCurveListDTO.EndPosDTO endPosDTO = new AdvancedCurveListDTO.EndPosDTO();

        startPosDTO.instanceName = startDistance.nodeName;
        startPosDTO.pos = new AdvancedCurveListDTO.StartPosDTO.PosDTO(startDistance.distanceX, startDistance.distanceY);
        endPosDTO.instanceName = endDistance.nodeName;
        endPosDTO.pos = new AdvancedCurveListDTO.EndPosDTO.PosDTO(endDistance.distanceX, endDistance.distanceY);
        advancedCurveListDTO.startPos = startPosDTO;
        advancedCurveListDTO.endPos = endPosDTO;

        AdvancedCurveListDTO.ControlPos1DTO controlPos1DTO = new AdvancedCurveListDTO.ControlPos1DTO();
        AdvancedCurveListDTO.ControlPos2DTO controlPos2DTO = new AdvancedCurveListDTO.ControlPos2DTO();
        controlPos1DTO.x = startDistance.distanceX + (endDistance.distanceX - startDistance.distanceX) / 3;
        controlPos2DTO.x = startDistance.distanceX + (endDistance.distanceX - startDistance.distanceX) * 2 / 3;
        controlPos1DTO.y = startDistance.distanceY + (endDistance.distanceY - startDistance.distanceY) / 3;
        controlPos2DTO.y = startDistance.distanceY + (endDistance.distanceY - startDistance.distanceY) * 2 / 3;
        controlPos1DTO.x = BigDecimal.valueOf(controlPos1DTO.x).setScale(3, RoundingMode.HALF_UP).doubleValue();
        controlPos2DTO.x = BigDecimal.valueOf(controlPos2DTO.x).setScale(3, RoundingMode.HALF_UP).doubleValue();
        controlPos1DTO.y = BigDecimal.valueOf(controlPos1DTO.y).setScale(3, RoundingMode.HALF_UP).doubleValue();
        controlPos2DTO.y = BigDecimal.valueOf(controlPos2DTO.y).setScale(3, RoundingMode.HALF_UP).doubleValue();
        advancedCurveListDTO.controlPos1 = controlPos1DTO;
        advancedCurveListDTO.controlPos2 = controlPos2DTO;

        AdvancedCurveListDTO.PropertyDTO propertyDTO = new AdvancedCurveListDTO.PropertyDTO();
        List<AdvancedCurveListDTO.PropertyDTO> propertyDTOS = new ArrayList<>();
        propertyDTO.key = "direction";
        propertyDTO.type = "int";
        if (backMove) {
            propertyDTO.value = "MQ==";
            propertyDTO.int32Value = 1;
        } else {
            propertyDTO.value = "MA==";
            propertyDTO.int32Value = 0;
        }
        propertyDTOS.add(propertyDTO);

        propertyDTO = new AdvancedCurveListDTO.PropertyDTO();
        propertyDTO.key = "movestyle";
        propertyDTO.type = "int";
        propertyDTO.value = "NQ==";
        //String moveStyle = MoveStyle.FIXED_TRACK_NAVIGATION.getCode();
        /*if(start.getUp()){
            if(start.getLeft() || start.getRight()){
                moveStyle = MoveStyle.QR_CODE_NAVIGATION.getCode();
            }
        }else if(start.getDown()){
            if(start.getLeft() || start.getRight()){
                moveStyle = MoveStyle.QR_CODE_NAVIGATION.getCode();
            }
        }else if(start.getLeft()){
            if(start.getUp() || start.getDown()){
                moveStyle = MoveStyle.QR_CODE_NAVIGATION.getCode();
            }
        }else if(start.getRight()){
            if(start.getUp() || start.getDown()){
                moveStyle = MoveStyle.QR_CODE_NAVIGATION.getCode();
            }
        }*/
        //propertyDTO.value = moveStyle;
        propertyDTO.int32Value = 5;
        propertyDTOS.add(propertyDTO);

        advancedCurveListDTO.property = propertyDTOS;

        advancedCurveListDTOS.add(advancedCurveListDTO);
        return advancedCurveListDTO;
    }

    /**
     * 地码
     * @param node
     * @param nodeDistanceMap
     * @return
     */
    private TagPosListDTO generalTag(Node node, Map<Node, NodeInformation> nodeDistanceMap, RgvMapTagDirection direction) {
        TagPosListDTO tagPosListDTO = new TagPosListDTO();
        NodeInformation nodeDistance = nodeDistanceMap.get(node);
        tagPosListDTO.tagValue = nodeDistance.tag;
        tagPosListDTO.x = nodeDistance.distanceX;
        tagPosListDTO.y = nodeDistance.distanceY;
        tagPosListDTO.angle = Math.PI * direction.getValue() / 180f;
        tagPosListDTO.isDMTDetected = true;
        return tagPosListDTO;
    }

    private Node findNode(int x, int y, int z, List<Node> nodes) {
        try {
            if (x < 0 || y < 0 || z < 0) {
                RgvLogger.info(LoggerType.WARN, null, "生成地图",
                        "解析坐标错误, x:" + x + ", y:" + y + ", z:" + z, this.getClass());
                return null;
            }

            for (Node node : nodes) {
                if (node.getX() == x && node.getY() == y && node.getZ() == z) {
                    return node;
                }
            }
        } catch (Exception e) {
            RgvLogger.exceptionLog(LoggerType.ERROR, null, "找不到节点：(" + x + "," + y + "," + z + ")",
                    e, this.getClass());
        }

        return null;
    }
}
